﻿using System;
using System.Collections.Generic;
using Wayloop.Web.Mvc.Models.Validation;


namespace Wayloop.Blog.Core.Data.Entities
{
    public abstract class AbstractEntity : IValidatableModel
    {
        /// <summary>
        /// Gets or sets the ID (primary key) of this instance.
        /// </summary>
        /// <value>The ID.</value>
        public virtual int Id { get; protected set; }


        /// <summary>
        /// Gets a value indicating whether this instance is transient.
        /// </summary>
        /// <value>
        ///     <c>true</c> if this instance is transient; otherwise, <c>false</c>.
        /// </value>
        protected bool IsTransient
        {
            get { return Id <= 0; }
        }


        /// <summary>
        /// Gets the entity signature.
        /// </summary>
        /// <returns>An array of property values that make up the "signature" of the entity.</returns>
        protected abstract object[] GetEntitySignature();


        /// <summary>
        /// Determines whether the specified <see cref="object"/> is equal to the current <see cref="object"/>.
        /// </summary>
        /// <param name="obj">The <see cref="object"/> to compare with the current <see cref="object"/>.</param>
        /// <returns>
        /// true if the specified <see cref="object"/> is equal to the current <see cref="object"/>; otherwise, false.
        /// </returns>
        /// <exception cref="ArgumentNullException">
        /// The <paramref name="obj"/> parameter is null.
        /// </exception>
        public override bool Equals(object obj)
        {
            if (obj == null) {
                throw new ArgumentNullException("obj");
            }

            var other = obj as AbstractEntity;
            if (other == null) {
                return false;
            }

            if (Id == other.Id) {
                return true;
            }

            if ((IsTransient || other.IsTransient) && GetHashCode() == other.GetHashCode()) {
                return true;
            }

            return ReferenceEquals(this, other);
        }


        /// <summary>
        /// Serves as a hash function for a particular type.
        /// </summary>
        /// <returns>
        /// A hash code for the current <see cref="object"/>.
        /// </returns>
        public override int GetHashCode()
        {
            int hashCode = GetType().FullName.GetHashCode();

            object[] signature = GetEntitySignature();
            foreach (var value in signature) {
                hashCode = (hashCode * 397) ^ ((value != null) ? value.GetHashCode() : 0);
            }

            return hashCode;
        }


        public virtual IDictionary<string, IList<string>> Validate()
        {
            return ValidationRunner.Validate(this);
        }


        public virtual IDictionary<string, IList<string>> Validate(Predicate<string> propertyFilter)
        {
            return ValidationRunner.Validate(this, propertyFilter);
        }
    }
}